(ns metabase.channel.settings
  (:require
   [clojure.string :as str]
   [java-time.api :as t]
   [metabase.settings.core :as setting :refer [defsetting]]
   [metabase.util.i18n :refer [deferred-tru tru]]
   [metabase.util.malli.registry :as mr]
   [metabase.util.malli.schema :as ms]
   [metabase.util.string :as u.str]))

(defsetting slack-token
  (deferred-tru
   (str "Deprecated Slack API token for connecting the Metabase Slack bot. "
        "Please use a new Slack app integration instead."))
  :deprecated "0.42.0"
  :encryption :when-encryption-key-set
  :visibility :settings-manager
  :doc        false
  :audit      :never
  :export?    false)

(defsetting slack-app-token
  (deferred-tru
   (str "Bot user OAuth token for connecting the Metabase Slack app. "
        "This should be used for all new Slack integrations starting in Metabase v0.42.0."))
  :encryption :when-encryption-key-set
  :visibility :settings-manager
  :getter (fn []
            (-> (setting/get-value-of-type :string :slack-app-token)
                (u.str/mask 9))))

(defn unobfuscated-slack-app-token
  "Get the unobfuscated value of [[slack-app-token]]."
  []
  (setting/get-value-of-type :string :slack-app-token))

(defsetting slack-token-valid?
  (deferred-tru
   (str "Whether the current Slack app token, if set, is valid. "
        "Set to ''false'' if a Slack API request returns an auth error."))
  :type       :boolean
  :visibility :settings-manager
  :doc        false
  :audit      :never)

(defsetting slack-cached-channels-and-usernames
  "A cache shared between instances for storing an instance's slack channels and users."
  :encryption :when-encryption-key-set
  :visibility :internal
  :type       :json
  :doc        false
  :audit      :never
  :export?    false)

(def zoned-time-epoch
  "Start of the UNIX epoch as a `ZonedDateTime`."
  (t/zoned-date-time 1970 1 1 0))

(defsetting slack-channels-and-usernames-last-updated
  "The updated-at time for the [[slack-cached-channels-and-usernames]] setting."
  :visibility :internal
  :cache?     false
  :type       :timestamp
  :default    zoned-time-epoch
  :doc        false
  :audit      :never
  :export?    false)

(defn process-files-channel-name
  "Converts empty strings to `nil`, and removes leading `#` from the channel name if present."
  [channel-name]
  (when-not (str/blank? channel-name)
    (if (str/starts-with? channel-name "#") (subs channel-name 1) channel-name)))

(defsetting slack-files-channel
  (deferred-tru "The name of the channel to which Metabase files should be initially uploaded")
  :deprecated "0.54.0"
  :default "metabase_files"
  :encryption :no
  :visibility :settings-manager
  :audit      :getter
  :setter (fn [channel-name]
            (setting/set-value-of-type! :string :slack-files-channel (process-files-channel-name channel-name))))

(defsetting slack-bug-report-channel
  (deferred-tru "The name of the channel where bug reports should be posted")
  :default "metabase-bugs"
  :encryption :no
  :visibility :settings-manager
  :audit      :getter
  :export?    false
  :setter (fn [channel-name]
            (setting/set-value-of-type! :string :slack-bug-report-channel (process-files-channel-name channel-name))))

(defsetting attachment-table-row-limit
  (deferred-tru "Maximum number of rows to render in an alert or subscription image.")
  :visibility :internal
  :type       :positive-integer
  :default    20
  :audit      :getter
  :getter     (fn []
                (let [value (setting/get-value-of-type :positive-integer :attachment-table-row-limit)]
                  (if-not (pos-int? value)
                    20
                    value)))
  :doc "Range: 1-100. To limit the total number of rows included in the file attachment
        for an email dashboard subscription, use MB_ATTACHMENT_ROW_LIMIT.")

(defsetting site-uuid-for-unsubscribing-url
  "UUID that we use for generating urls users to unsubscribe from alerts. The hash is generated by
  hash(secret_uuid + email + subscription_id) = url. Do not use this for any other applications. (See #29955)"
  :encryption :when-encryption-key-set
  :visibility :internal
  :base       setting/uuid-nonce-base)

(defsetting notification-link-base-url
  (deferred-tru "By default \"Site Url\" is used in notification links, but can be overridden.")
  :encryption :no
  :visibility :internal
  :type       :string
  :feature    :whitelabel
  :audit      :getter
  :doc "The base URL where dashboard notitification links will point to instead of the Metabase base URL.
        Only applicable for users who utilize interactive embedding and subscriptions.")

(defsetting email-from-address
  (deferred-tru "The email address you want to use for the sender of emails.")
  :encryption :no
  :default    "notifications@metabase.com"
  :visibility :settings-manager
  :audit      :getter)

(defsetting email-from-address-override
  (deferred-tru "The email address you want to use for the sender of emails from your custom SMTP server.")
  :encryption :no
  :feature   :cloud-custom-smtp
  :default    "notifications@metabase.com"
  :visibility :settings-manager
  :export?    false
  :audit      :getter)

(defsetting email-from-name
  (deferred-tru "The name you want to use for the sender of emails.")
  :encryption :no
  :visibility :settings-manager
  :audit      :getter
  :setter     (fn [new-value]
                ;; Validate based on chars in https://www.ietf.org/rfc/rfc5322.txt via https://docs.aws.amazon.com/ses/latest/dg/send-email-concepts-email-format.html
                (when (and new-value (re-matches #".*[()<>\[\]:;@/\\,\.\"].*" new-value))
                  (throw (ex-info (tru "Invalid special character included.") {:status-code 400})))
                (setting/set-value-of-type! :string :email-from-name new-value)))

(defsetting bcc-enabled?
  (deferred-tru "Whether or not bcc emails are enabled, default behavior is that it is")
  :visibility :settings-manager
  :type       :boolean
  :default    true)

(def ^:private ReplyToAddresses
  [:maybe [:sequential ms/Email]])

(def ^:private ^{:arglists '([reply-to-addresses])} validate-reply-to-addresses
  (mr/validator ReplyToAddresses))

(defsetting email-reply-to
  (deferred-tru "The email address you want the replies to go to, if different from the from address.")
  :encryption :no
  :type       :json
  :visibility :settings-manager
  :audit      :getter
  :setter     (fn [new-value]
                (if (validate-reply-to-addresses new-value)
                  (setting/set-value-of-type! :json :email-reply-to new-value)
                  (throw (ex-info "Invalid reply-to address" {:value new-value})))))

(defsetting email-smtp-host
  (deferred-tru "The address of the SMTP server that handles your emails.")
  :encryption :when-encryption-key-set
  :visibility :settings-manager
  :audit      :getter)

(defsetting email-smtp-host-override
  (deferred-tru "The address of the custom SMTP server that handles your emails.")
  :encryption :when-encryption-key-set
  :feature   :cloud-custom-smtp
  :visibility :settings-manager
  :export?    false
  :audit      :getter)

(defsetting email-smtp-username
  (deferred-tru "SMTP username.")
  :encryption :when-encryption-key-set
  :visibility :settings-manager
  :audit      :getter)

(defsetting email-smtp-username-override
  (deferred-tru "Custom SMTP server username.")
  :encryption :when-encryption-key-set
  :feature   :cloud-custom-smtp
  :visibility :settings-manager
  :export?    false
  :audit      :getter)

(defsetting email-smtp-password
  (deferred-tru "SMTP password.")
  :encryption :when-encryption-key-set
  :visibility :settings-manager
  :sensitive? true
  :audit      :getter)

(defsetting email-smtp-password-override
  (deferred-tru "Custom SMTP server password.")
  :encryption :when-encryption-key-set
  :feature   :cloud-custom-smtp
  :visibility :settings-manager
  :sensitive? true
  :export?    false
  :audit      :getter)

(defsetting email-smtp-port
  (deferred-tru "The port your SMTP server uses for outgoing emails.")
  :encryption :when-encryption-key-set
  :type       :integer
  :visibility :settings-manager
  :audit      :getter)

(defsetting email-smtp-port-override
  (deferred-tru "The port your custom SMTP server uses for outgoing emails. Only ports 465, 587, and 2525 are supported.")
  :encryption :when-encryption-key-set
  :type :integer
  :feature :cloud-custom-smtp
  :visibility :settings-manager
  :audit :getter
  :export?    false
  :setter (fn [new-value]
            (when (some? new-value)
              (assert (#{465 587 2525} new-value)
                      (tru "Invalid custom email-smtp-port! Only SMTP ports of 465, 587, or 2525 are allowed.")))
            (setting/set-value-of-type! :integer :email-smtp-port-override new-value)))

(defsetting email-smtp-security
  (deferred-tru "SMTP secure connection protocol. (tls, ssl, starttls, or none)")
  :encryption :when-encryption-key-set
  :type       :keyword
  :default    :none
  :visibility :settings-manager
  :audit      :raw-value
  :setter     (fn [new-value]
                (when (some? new-value)
                  (assert (#{:tls :ssl :none :starttls} (keyword new-value))))
                (setting/set-value-of-type! :keyword :email-smtp-security new-value)))

(defsetting email-smtp-security-override
  (deferred-tru "SMTP secure connection protocol for your custom server. (tls, ssl, or starttls)")
  :encryption :when-encryption-key-set
  :feature    :cloud-custom-smtp
  :type       :keyword
  :default    :ssl
  :visibility :settings-manager
  :audit      :raw-value
  :export?    false
  :setter     (fn [new-value]
                (when (some? new-value)
                  (assert (#{:tls :ssl :starttls} (keyword new-value))
                          (tru "Invalid email-smtp-security-override! Only values of tls, ssl, and starttls are allowed.")))
                (setting/set-value-of-type! :keyword :email-smtp-security-override new-value)))

(defsetting smtp-override-enabled
  (deferred-tru "Whether to use the custom SMTP server rather than the standard settings.")
  :encryption :no
  :feature    :cloud-custom-smtp
  :type       :boolean
  :default    false
  :visibility :settings-manager
  :audit      :getter
  :export?    false
  :setter     (fn [new-value]
                (when (and new-value (not (setting/get-value-of-type :string :email-smtp-host-override)))
                  (throw (ex-info (tru "Cannot enable smtp-override when it is not configured.") {:status-code 400})))
                (setting/set-value-of-type! :boolean :smtp-override-enabled new-value)))

(defsetting email-max-recipients-per-second
  (deferred-tru "The maximum number of recipients, summed across emails, that can be sent per second.
                Note that the final email sent before reaching the limit is able to exceed it, if it has multiple recipients.")
  :export?    true
  :type       :integer
  :visibility :settings-manager
  :audit      :getter)

(defsetting email-configured?
  "Check if email is enabled and that the mandatory settings are configured."
  :type       :boolean
  :visibility :public
  :setter     :none
  :getter     #(boolean (email-smtp-host))
  :doc        false)

(defsetting surveys-enabled
  (deferred-tru "Enable or disable surveys")
  :type       :boolean
  :default    true
  :export?    false
  :visibility :internal
  :audit      :getter)

(defsetting http-channel-host-strategy
  (deferred-tru (str "Controls which types of hosts are allowed as HTTP channel destinations.\n"
                     "Options:\n"
                     "- external-only (default - only external hosts)\n"
                     "- allow-private (external + private networks but NOT localhost)\n"
                     "- allow-all (no restrictions including localhost).\n"))
  :type       :keyword
  :visibility :internal
  :default    :external-only
  :export?    false
  :setter     (fn [new-value]
                (when (some? new-value)
                  (assert (#{:external-only :allow-private :allow-all} (keyword new-value))
                          (tru "Invalid http-channel-host-strategy! Only values of external-only, allow-private, and allow-all are allowed.")))
                (setting/set-value-of-type! :keyword :http-channel-host-strategy new-value)))
